/*********************************************************************
* 
* S3PICPROG -  PIC programmer
* Copyright (C) 2008 Olivier Ringot <oringot@gmail.com>
* 
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* 
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* 
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, 
* Boston, MA  02110-1301, USA.
* 
**********************************************************************
* 
* $Revision: $
* $Date: $
* $Source: $
* $Log: $
*
*********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include "icsp.h"

int main(int argc,char **argv)
{
  FILE *file_in;
  unsigned int i,j,k,p,blank,length,record,index,addrl,
               addrh,data,cksum,hex_done,config,hit_error;
  
  unsigned char byte_buffer[5];
  unsigned char code_buffer[32*1024];
  unsigned char config_buffer[16];
 
  unsigned char c;
  char line_buffer[256];
  char          *s;
  
  icsp_ttys=open("/dev/ttyS0",O_RDWR);
  if(icsp_ttys==-1)
  {
    printf("error: unable to open /dev/ttyS0\n");
    exit(-1);
  }
  
  if(argc!=2)
  {
    printf("error: bad argument number !\n");
    printf("usage: %s <file.hex>\n",argv[0]);
    exit(-1);
  }
  
  /* clean buffer */
  for(i=0;i<32*1024;i++)
  {
    code_buffer[i]=0xff;  
  }
  
  for(i=0;i<16;i++)
  {
    config_buffer[i]=0xff;  
  }
  
  /* fill buffer */
  
  file_in = fopen(argv[1],"rb");
  if(!file_in)
  {
    printf("error: unable to open file %s \n",argv[1]);
    exit(-1);
  }
  
  /* init */
  addrh = 0;
  hex_done = 0;
  config = 0;
  hit_error = 0;
  
  while(1)
  {
    /* end of file, exit */
    if(feof(file_in))
    {
      break;
    }
    
    /* load line in buffer */
    fgets(line_buffer,256,file_in);
    
    /* check leading character */
    p=0;
    if(line_buffer[p++]!=':')
    {
      printf("error: invalid file format !\n");
      exit(-1);
    }

    /* payload length */
    byte_buffer[0]=line_buffer[p++];
    byte_buffer[1]=line_buffer[p++];
    byte_buffer[2]=0;
    length = strtoul(byte_buffer,(char**)NULL,16);
 
    /* base address */
    byte_buffer[0]=line_buffer[p++];
    byte_buffer[1]=line_buffer[p++];
    byte_buffer[2]=line_buffer[p++];
    byte_buffer[3]=line_buffer[p++];
    byte_buffer[4]=0;
    addrl = strtoul(byte_buffer,(char**)NULL,16);
   
    byte_buffer[0]=line_buffer[p++];
    byte_buffer[1]=line_buffer[p++];
    byte_buffer[2]=0;
    record = strtoul(byte_buffer,(char**)NULL,16);
    
    switch(record)
    {
      /* 8 bits data */
      case 0x00:
      
        for(i=0;i<length;i++)
        {
          byte_buffer[0]=line_buffer[p++];
          byte_buffer[1]=line_buffer[p++];
          byte_buffer[2]=0;
          data = strtoul(byte_buffer,(char**)NULL,16);
        
          index = ((addrh<<16)| addrl) + i;
          
          if(index<32*1024)
          {
            code_buffer[index] = data;
          }
          else
          {
            if(addrh==0x0030)
            {
              config=1;
              config_buffer[index&0xffff] = data;
            }
            else
            {
              printf("error: unsupported address range 0x%-.6x\n",index&0xffffff);
              exit(-1);
            } 
          }
        }
        break;
        
      /* 16 bits data */
      case 0x04:
        byte_buffer[0]=line_buffer[p++];
        byte_buffer[1]=line_buffer[p++];
        byte_buffer[2]=line_buffer[p++];
        byte_buffer[3]=line_buffer[p++];
        byte_buffer[4]=0;
        addrh = strtoul(byte_buffer,(char**)NULL,16);
        break;
        
      /* end of hex file */
      case 0x01:
      
        hex_done=1;
        break;
        
      default:
        printf("error: record filed 0x%-.2x not supported !\n",record);
        exit(-1);
    }
    
    byte_buffer[0]=line_buffer[p++];
    byte_buffer[1]=line_buffer[p++];
    byte_buffer[2]=0;
    cksum = strtoul(byte_buffer,(char**)NULL,16);
  
    if(hex_done) break;
  
  }
  
  /*
  for(i=0;i<32*1024;i+=16)
  {
    printf("%-.6x",i);
    
    for(j=0;j<16;j++)
    {
      printf(" %-.2x",code_buffer[j+i]);
    }
    printf("\n");
  }
  */
  
  /* init delay */
  icsp_delays(4,4,4);
  
  /* enter ICSP */
  icsp_enter();
  
  /* erase PIC memory */
  
  printf("erasing memory...\r");
  icsp_write_tblptr(0x3c0005);
  icsp_write(0x3f3f);
  icsp_write_tblptr(0x3c0004);
  icsp_write(0x8f8f);
  icsp_core_instr(0x0000);
  icsp_delays(0x0004,0x0004,0x7c83);
  icsp_core_instr(0x0000);
  icsp_delays(0x0004,0x0004,0x0004);
  printf("erasing memory done.\n");
 
  /* program PIC 18F2550 */
  
  for(i=0;i<32*1024;i+=32)
  {
    /* check if 32 bytes chunk needs to be programmed */
    blank=1;
    for(j=0;j<32;j++)
    {
      if(code_buffer[i+j]!=0xff)
      {
        blank=0;
        break;
      }
    }
    
    if(blank==0)
    {
      /* program TBLPTR */
      icsp_write_tblptr(i);
      
      /* write buffer */
      icsp_write32_prog(code_buffer+i);
      
      /* change delays */
      icsp_delays(0x0004,0x186a,0x0271);
      
      /* NOP */
      printf("code programming addr=0x%-.6x\r",i&0xffffff);
      icsp_core_instr(0x0000);
      
      /* restore delays */
      icsp_delays(0x0004,0x0004,0x0004);
    }
  }
  printf("code programming done.         \n");

  /* configuration PIC 18F2550 */
  if(config==1)
  {
    printf("config bits programming...\r");
    for(i=0;i<16;i++)
    {
      icsp_write_tblptr(0x300000+i);
      icsp_write_prog((config_buffer[i]&0xff)|((config_buffer[i]&0xff)<<8));
      icsp_delays(0x0004,0x186a,0x0271);
      icsp_core_instr(0x0000);
      icsp_delays(0x0004,0x0004,0x0004);
    }
    printf("config bits programming done\n");
  
    /* CONFIG verifying */
    printf("config bits verifying...\r");
    hit_error=0;
    for(i=0;i<16;i++)
    {
      if(i==4 || i==7 || i==0xe || i==0xf)
      {
      }
      else
      {
        icsp_write_tblptr(0x300000+i);
        data=icsp_read();
        if(data!=config_buffer[i])
        {
          printf("mismatch at 0x%-.6x, read 0x%-.2x, expected 0x%-.2x\n",
                 0x300000+i,data,config_buffer[i]);
          hit_error++;
        }
      }
    }
  
    if(hit_error)
    {
      printf("config bits programmed with %d error(s) !\n",hit_error);
    }
    else
    {
      printf("config bits programmed and verified.\n");
    }
  }
  else
  {
    printf("warning: no configuration register defined !\n");
  }

  /* leave ICSP */
  icsp_exit();
  
  fclose(file_in);
  
  close(icsp_ttys);
}
